package com.gitee.ixtf.vertx.internal

import cn.hutool.core.exceptions.ExceptionUtil
import cn.hutool.log.Log
import com.gitee.ixtf.vertx.InputStreamState
import com.gitee.ixtf.vertx.nonLogT
import io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE
import io.netty.handler.codec.http.HttpHeaderValues.APPLICATION_JSON
import io.netty.handler.codec.http.HttpHeaderValues.TEXT_PLAIN
import io.vertx.core.Future
import io.vertx.core.Handler
import io.vertx.core.buffer.Buffer
import io.vertx.core.json.Json
import io.vertx.core.json.JsonArray
import io.vertx.core.json.JsonObject
import io.vertx.core.streams.ReadStream
import io.vertx.ext.web.RoutingContext
import io.vertx.kotlin.coroutines.coAwait
import java.io.File
import java.io.InputStream
import java.nio.file.Path
import java.util.*
import java.util.concurrent.CompletionStage
import kotlin.io.path.absolutePathString
import kotlin.reflect.full.callSuspend
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.future.await
import kotlinx.coroutines.reactor.awaitSingle
import kotlinx.coroutines.reactor.awaitSingleOrNull
import kotlinx.coroutines.reactor.mono
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono

fun RoutingContext.failure(log: Log, t: Throwable, msg: String = request().path()) {
  val rootCause = ExceptionUtil.getRootCause(t) ?: t
  fail(rootCause)
  if (!nonLogT(t)) log.error(msg, rootCause)
}

private suspend fun RoutingContext.convertInvoke(o: Any?): Any =
    when (o) {
      null,
      Unit -> Buffer.buffer()
      is Buffer,
      is String,
      is File,
      is Path,
      is ReadStream<*> -> o
      is InputStream -> InputStreamState { o }
      is ByteArray -> Buffer.buffer(o)
      is JsonObject -> o.toBuffer()
      is JsonArray -> o.toBuffer()
      is Future<*> -> convertInvoke(o.coAwait())
      is CompletionStage<*> -> convertInvoke(o.await())
      is Optional<*> -> convertInvoke(o.orElse(null))
      is Mono<*> -> convertInvoke(o.awaitSingleOrNull())
      is Flux<*> -> convertInvoke(o.collectList().awaitSingle())
      is Flow<*> -> convertInvoke(o.toList())
      else -> Json.encodeToBuffer(o)
    }

private fun RoutingContext.success(o: Any?) {
  response()
      .takeUnless { it.ended() }
      ?.apply {
        val hasContentType = headers()?.contains(CONTENT_TYPE) == true
        when (o) {
          null,
          Unit -> end()
          is Buffer -> {
            if (o.length() > 0 && !hasContentType)
                putHeader(CONTENT_TYPE, acceptableContentType ?: APPLICATION_JSON)
            end(o)
          }
          is String -> {
            if (!hasContentType) putHeader(CONTENT_TYPE, acceptableContentType ?: TEXT_PLAIN)
            end(o)
          }
          is File -> sendFile(o.absolutePath)
          is Path -> sendFile(o.absolutePathString())
          is ReadStream<*> -> send(o as ReadStream<Buffer>)
          else -> fail(500)
        }
      }
}

open class DefaultRestfulHandler(o: RestfulMethod) : ApiMethodHandler(o), Handler<RoutingContext> {
  override fun handle(rc: RoutingContext) {
    mono {
          val args = converters.map { it.convert(rc) }.toTypedArray()
          val invoke = method.invoke(instance, *args)
          rc.convertInvoke(invoke)
        }
        .subscribe({ rc.success(it) }, { rc.failure(log, it) })
  }
}

class SuspendRestfulHandler(o: RestfulMethod) : ApiMethodHandler(o), Handler<RoutingContext> {
  override fun handle(rc: RoutingContext) {
    mono {
          val args = converters.map { it.convert(rc) }.toTypedArray()
          val invoke = kotlinFunction.callSuspend(instance, *args)
          rc.convertInvoke(invoke)
        }
        .subscribe({ rc.success(it) }, { rc.failure(log, it) })
  }
}
